#include <linux/limits.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <string.h>
#include <errno.h>

#include <vector>
#include <stdexcept>

#include "commonexception.h"
#include "filepath-util.h"

#ifdef COMMON_GTEST
#include <gtest/gtest.h>
#endif

using std::vector;
using std::runtime_error;


////////////////////////////RawPath Implement Begin/////////////////////////////

RawPath& RawPath::append(const char *src,size_t size)
{
    if (src != nullptr && size != 0) {
        if (buffer_.back_s() != '/' && *src != '/')
            buffer_.append('/');
        buffer_.append(src,size);
    }
    return *this;
}

void RawPath::followDotDot()
{
    removeTrailSlash();
    const char * const lastSlashPtr = static_cast<const char*>(memrchr(buffer_.constRawData(),'/',buffer_.size()));
    if (lastSlashPtr == nullptr) {
        if (buffer_.size() == 1 && buffer_.back_s() == '.')
            buffer_.append("/..",3);
         else
            buffer_.assign(".",1);
    } else {
        const char * const bufferEndPtr = buffer_.constRawData() + buffer_.size();
        size_t leftSize = bufferEndPtr - lastSlashPtr;
        if (leftSize == 3 && lastSlashPtr[1] == '.' && lastSlashPtr[2] == '.') {
            buffer_.append("/..",3);
        } else if (leftSize == 2 && lastSlashPtr[1] == '.') {
            buffer_.resize(lastSlashPtr - buffer_.constRawData());
            if (buffer_.isEmpty())
                buffer_.append('/');
            followDotDot();
        } else {
            buffer_.resize(lastSlashPtr - buffer_.constRawData());
            if (buffer_.isEmpty())
                buffer_.append('/');
        }
    }
    return ;
}

RawPath& RawPath::assign(const char *src,size_t size)
{
    if (src == nullptr || size == 0) {
        buffer_.assign("/",1);
    } else {
        buffer_.assign(src,size);
    }
    return *this;
}

RawPath& RawPath::removeTrailSlash()
{
    while (buffer_.size() > 1 && buffer_.back_s() == '/')
        buffer_.popBack_s();
    return *this;
}

#ifdef COMMON_GTEST
namespace /* RawPathGTestNS */ {

char PATH_BUF[PATH_MAX];

TEST(RawPathTest,RemoveTrailSlashTest) {
    RawPath path {PATH_BUF,sizeof(PATH_BUF)};
    path.assign("/");
    EXPECT_STREQ("/",path.removeTrailSlash().constData());
    path.assign("///");
    EXPECT_STREQ("/",path.removeTrailSlash().constData());
    path.assign("/home/");
    EXPECT_STREQ("/home",path.removeTrailSlash().constData());
    path.assign("/home///");
    EXPECT_STREQ("/home",path.removeTrailSlash().constData());
    path.assign("/home");
    EXPECT_STREQ("/home",path.removeTrailSlash().constData());
}

TEST(RawPathTest,AppendSlashTest) {
    RawPath path {PATH_BUF,sizeof(PATH_BUF)};
    path.assign("/");
    EXPECT_STREQ("/",path.appendSlash().constData());
    path.assign("///");
    EXPECT_STREQ("///",path.appendSlash().constData());
    path.assign("/home");
    EXPECT_STREQ("/home/",path.appendSlash().constData());
    path.assign("/home/");
    EXPECT_STREQ("/home/",path.appendSlash().constData());
}

TEST(RawPathTest,ContainsTest) {
    RawPath path {PATH_BUF,sizeof(PATH_BUF),"/home/wangwei/hello"};
    EXPECT_EQ(path.constRawData(),path.contains(nullptr));
    EXPECT_EQ(nullptr,path.contains("heworld"));
    EXPECT_EQ(path.constRawData() + 6,path.contains("wangwei"));
}

TEST(RawPathTest,isIn) {
    RawPath path {PATH_BUF,sizeof(PATH_BUF),"/home"};
    EXPECT_EQ(path.constRawData(),path.isIn("/"));
    EXPECT_EQ(path.constRawData(),path.isIn("///"));

    path.assign("/homewangwei");
    EXPECT_EQ(nullptr,path.isIn("/home"));
    EXPECT_EQ(nullptr,path.isIn("/home//"));

    path.assign("/home/wangwei");
    const char * const expectedVal = path.constRawData() + 5;
    EXPECT_EQ(expectedVal,path.isIn("/home"));
    EXPECT_EQ(expectedVal,path.isIn("/home/"));
    EXPECT_EQ(expectedVal,path.isIn("/home///"));

    path.assign("/");
    EXPECT_EQ(path.constRawData(),path.isIn("/"));
    EXPECT_EQ(path.constRawData(),path.isIn("///"));

    path.assign("/home");
    EXPECT_EQ(path.constRawData() + path.size(),path.isIn("/home"));
    EXPECT_EQ(path.constRawData() + path.size(),path.isIn("/home/"));
    EXPECT_EQ(path.constRawData() + path.size(),path.isIn("/home///"));
}

} // RawPathGTestNS

#endif


////////////////////////////RawPath Implement End///////////////////////////////






////////////////////////////PathUtil Implement Begin////////////////////////////
namespace {
// 符号链接的最大数量,LINUX 内核与 GLIBC 使用的值是 40.
constexpr int num_links_max = 33;

enum RetEnum:unsigned char {
    NOT_EXISTS = 0,
    EXISTS = 1,
    UNKNOWN = 2
};
enum TypeEnum:unsigned int {
    DIRType = S_IFDIR,
    SYMLINK = S_IFLNK,
    OTHER
};

struct LStatCache {
    void follow_dotdot(char *&rpath,char *&rdst);
    void do_follow_dotdot(char *&rpath,char *&rdst);
    void get_rpath_lstat(RetEnum &lstat_ret,TypeEnum &type,const char *rpath);
    void get_rpath_lstat(RetEnum &lstat_ret,TypeEnum &type);
    void do_append_componnet();
    void init2root();
private:
    vector<RetEnum> retvec;
    vector<TypeEnum> typevec;
};

void LStatCache::do_append_componnet()
{
    RetEnum ret = retvec.back();
    retvec.push_back(ret == NOT_EXISTS ? NOT_EXISTS : UNKNOWN);
    typevec.push_back(OTHER);
    return ;
}

void LStatCache::init2root()
{
    retvec.resize(0);
    typevec.resize(0);
    retvec.push_back(EXISTS);
    typevec.push_back(DIRType);
    return ;
}


inline void LStatCache::follow_dotdot(char *&rpath,char *&rdst)
{
    if (rdst != rpath)
        do_follow_dotdot(rpath,rdst);
    return ;
}

void LStatCache::do_follow_dotdot(char *&rpath,char *&rdst)
{
    do {
        --rdst;
    } while (*rdst != '/');
    retvec.pop_back();
    typevec.pop_back();
    return ;
}

void LStatCache::get_rpath_lstat(RetEnum &lstat_ret,TypeEnum &type,const char *rpath)
{
    if (retvec.back() == UNKNOWN) {
//#ifndef NDEBUG
//        printf("lstat(%s)\n",rpath);
//#endif
        struct stat component_stat;
        int ret = lstat(rpath,&component_stat);
        if (ret < 0 && errno != ENOENT)
            THROW(errno,"lstat");
        retvec.back() = ret < 0 ? NOT_EXISTS : EXISTS;
        switch (component_stat.st_mode & S_IFMT) {
        case S_IFDIR:
            typevec.back() = DIRType;
            break;
        case S_IFLNK:
            typevec.back() = SYMLINK;
            break;
        default:
            typevec.back() = OTHER;
            break;
        }
    }
//#ifndef NDEBUG
//    else {
//        printf("lstat(%s):hints\n",rpath);
//    }
//#endif

    lstat_ret = retvec.back();
    type = typevec.back();
    return ;
}

inline void LStatCache::get_rpath_lstat(RetEnum &lstat_ret,TypeEnum &type)
{
    lstat_ret = retvec.back();
    type = typevec.back();
    return ;
}


} // namespace ;

size_t PathUtil::readlink(char *dst,size_t max_size,const char *path)
{
    if (path == nullptr || (dst == nullptr && max_size != 0))
        THROW(EINVAL);

    ssize_t ret = 0;
    if (dst != nullptr && max_size != 0) {
        ret = ::readlink(path,dst,max_size);
        if (ret < 0)
            THROW(errno,"readlink");
    }
    if (static_cast<size_t>(ret) == max_size) {
        struct stat filestat;
        if (lstat(path,&filestat) == -1)
            THROW(errno,"lstat");
        if (static_cast<size_t>(filestat.st_size) > max_size)
            THROW_RANGE_EXCEPTION(filestat.st_size,max_size);
    }
    return ret;
}

/* 从 GLIBC 的 realpath 实现可知:
 * * 普通 component;表明即非'.',也不是'..'的 component.
 * * 不变量:rpath,rpath_end 存放着当前已经解析出来的部分,该部分总是没有多余的符号链接,'.',
 *    '..','/';以及总不带末尾'/'.并且当且仅当 rpath 中为空时,表明 rpath 中存放着根目录.
 * * 当遇到普通 component 时,将其先复制到 dst 中;若缓冲区空间不足,若 dst 由用户分配,则报错.
 *   否则分配至少 PATH_MAX 字节的空间.
 * * 对符号链接的处理:
 *      if (符号链接出现的次数超过一定的值)
 *          出错.
 *      else
 *          读取其内容到临时缓冲区中.
 *          将符号链接文件内容与 ptr 中未解析的部分拼接在一起.
 *          若拼接后的结果为绝对路径,则置 dst 为空(表明是根目录);
 *          若不是绝对路径,则对 dst 回退一级,此时表明拼接后的结果是相对于 dst 中当前内容的.
 */
size_t PathUtil::advancedRealpath(char *dst,size_t max_size,
                       const char *ptr,size_t size,
                       FollowLinkVal fl,ComponentExistsVal ce)
{
    if (dst == nullptr || max_size == 0)
        THROW_RANGE_EXCEPTION(PATH_MAX,0);
    if (ptr == nullptr || size == 0)
        THROW(EINVAL,"输入路径为空");
    if (!isAbsolute(ptr,size))
        THROW(EINVAL,"输入路径不是绝对路径");
    if (!not_overlap(dst,max_size,ptr,size))
        THROW(EINVAL,"输入,输出缓冲区重叠");

    /* 缓存 lstat() 调用结果,减少对 lstat() 的调用次数.
     * 不变量: lstat_cache[.size() - 1] 始终存放着 rpath 对应的 lstat 结果.
     * 1;retvec 值说明:
     * o NOT_EXISTS;表示文件不存在.EXISTS,表示文件存在;UNKNOWN,未知.
     * 2;typevec 仅当 retvec 为 EXISTS 时,才有意义.
     */
    LStatCache lstat_cache;

    /* 不变量,rdst 始终指向着下一个待写入的位置,若: rdst == rpath,则表明当前 rpath 中存放
     * 着根目录;否则 [rpath,rdst) 中存放着绝对路径,并且不包含符号链接,'.','..',以及末尾'/'.
     */
    char *rpath = dst;
    char *rdst = dst;
    char *rpath_end = rpath + max_size;
    lstat_cache.init2root();

    /* 在每次 for 循环开始时,都要检测 unhandle 的值:
     *  if (unhandle)
     *      则表明 [rpath,rdst) 中存放的路径尚未处理.
     *      而且 *rdst='\0',所以 rpath 可以视为 C 字符串.
     *  else
     *      表明已经处理.
     */
    bool unhandle = false;
    bool last_component = false;

    char symlink_file_content_buf[PATH_MAX];
    char another_ptr_buf[PATH_MAX];
    RawByteArray symlink_file_content {symlink_file_content_buf,sizeof(symlink_file_content_buf)};
    RawByteArray another_ptr {another_ptr_buf,sizeof(another_ptr_buf)};
    size_t links_num = 0;

    const char * ptr_end = ptr + size;
    for (const char *start = ptr,*end = ptr; ; start = end) {
        /* 循环不变量,start(或 end) 始终指向着'/',或者等于 ptr_end.
         * [start,end);确定了下一个待处理的 component.
         */
        while (start < ptr_end && *start == '/')
            ++start;

        if (unhandle) {
            unhandle = false;
            size_t start_size = ptr_end - start;
            last_component = (start_size == 0);
            RetEnum lstat_ret;
            TypeEnum type;
            lstat_cache.get_rpath_lstat(lstat_ret,type,rpath);

            if (!last_component) {
                if (lstat_ret == NOT_EXISTS) {
                    if (ce != ALL_NEED_NOT_EXISTS)
                        THROW(ENOENT,"%s",rpath);
                } else if (type == SYMLINK) {
                    if (++links_num > num_links_max)
                        THROW(ELOOP,"%s",rpath);
                    symlink_file_content.resize(0);
                    readlink(symlink_file_content,rpath);
                    size_t another_ptr_size = symlink_file_content.size() +
                            1 + start_size;
                    if (another_ptr_size > another_ptr.size())
                        another_ptr.resize(another_ptr_size);
                    memmove(another_ptr.data() + (another_ptr_size - start_size),
                            start,start_size);
                    char *tmp_ptr = static_cast<char*>(mempcpy(
                            another_ptr.data(),
                            symlink_file_content.constData(),
                            symlink_file_content.size()));
                    *tmp_ptr = '/';
                    another_ptr.resize(another_ptr_size);

                    end = another_ptr.data();
                    ptr_end = end + another_ptr_size;

                    if (isAbsolute(end,another_ptr_size)) {
                        rdst = rpath;
                        lstat_cache.init2root();
                    } else {
                        lstat_cache.do_follow_dotdot(rpath,rdst);
                    }
                    continue;
                } else if (type != DIRType) {
                    THROW(ENOTDIR,"%s",rpath);
                }
            } else {
                if (lstat_ret == NOT_EXISTS) {
                    if (ce == ALL_EXISTS)
                        THROW(ENOENT,"%s",rpath);
                } else if (type == SYMLINK && fl == FOLLOW_LINK) {
                    if (++links_num > num_links_max)
                        THROW(ELOOP,"%s",rpath);
                    symlink_file_content.resize(0);
                    readlink(symlink_file_content,rpath);
                    size_t another_ptr_size = symlink_file_content.size();
                    another_ptr = symlink_file_content;

                    end = another_ptr.data();
                    ptr_end = end + another_ptr_size;

                    if (isAbsolute(end,another_ptr_size)) {
                        rdst = rpath;
                        lstat_cache.init2root();
                    } else {
                        lstat_cache.do_follow_dotdot(rpath,rdst);
                    }
                    continue;
                }
                break;
            }
        } else if (start == ptr_end) {
            break;
        }
        for (end = start; end < ptr_end && *end != '/'; ++end)
            /*  */;

        // 处理 [start,end) 确定的 component.
        size_t component_size = end - start;
        if (component_size == 1 && start[0] == '.')
            continue;
        else if (component_size == 2 && start[0] == '.' && start[1] == '.') {
            lstat_cache.follow_dotdot(rpath,rdst);
        } else {
            *rdst = '/';
            ++rdst;
            if (rdst + component_size + 1 > rpath_end) {
                THROW_RANGE_EXCEPTION( (rdst - rpath) + (component_size + 1 <= PATH_MAX) ?
                                        PATH_MAX :
                                        component_size + 1,max_size);
            }
            rdst = static_cast<char*>(mempcpy(rdst,start,component_size));
            *rdst = '\0';
            unhandle = true;
            lstat_cache.do_append_componnet();
        }
    }

    TypeEnum type;
    RetEnum lstat_ret;
    lstat_cache.get_rpath_lstat(lstat_ret,type);
    if (((lstat_ret != EXISTS || type != DIRType) && ptr[size - 1] == '/') || rdst == rpath) {
        *rdst = '/';
        ++rdst;
    }
    return rdst - rpath;
}

bool PathUtil::extensionIs(const char *filename,size_t filenamesize,
    const char *name,size_t size)
{
    const char * const lastComponentPtr = lastComponent(filename,filenamesize);
    if (lastComponentPtr == nullptr)
        return false;
    size_t const lastComponentSize = (filename + filenamesize) - lastComponentPtr;

    return (!endWith(lastComponentPtr,lastComponentSize,name,size)) ? false :
        lastComponentSize > size && lastComponentPtr[lastComponentSize - size - 1] == '.';
}

const char* PathUtil::lastComponent(const char *path,size_t size)
{
    const char *lastComponentPtr = static_cast<const char*>(memrchr(path,'/',size));
    if (lastComponentPtr == nullptr)
        return path;
    ++lastComponentPtr;
    if (lastComponentPtr == path + size)
        return nullptr;
    return lastComponentPtr;
}

const char* PathUtil::isIn(const char *p,size_t psize,const char *s,size_t ssize)
{
    psize = getTrailSlashPtr(p,psize) - p;
    if (psize == 1)
        return s;

    if (!beginWith(s,ssize,p,psize))
        return nullptr;
    s += psize;
    return ssize == psize || *s == '/' ? s : nullptr;
}

const char* PathUtil::getTrailSlashPtr(const char *src,size_t size)
{
    if (src == nullptr || size == 0)
        return src;

    const char * ptr = src + (size - 1);
    while (ptr >= src && *ptr == '/')
        --ptr;
    ++ptr;
    return ptr == src ? ptr + 1 : ptr;
}

#ifdef COMMON_GTEST

namespace /* PathUtilTestNS */{

TEST(PathUtilTest,getTrailSlashPtr) {
    const char *path = "/";
    EXPECT_STREQ("/",std::string(path,PathUtil::getTrailSlashPtr(path)).data());
    path = "///";
    EXPECT_STREQ("/",std::string(path,PathUtil::getTrailSlashPtr(path)).data());

    path = "///a";
    EXPECT_STREQ("///a",std::string(path,PathUtil::getTrailSlashPtr(path)).data());
    path = "///a/";
    EXPECT_STREQ("///a",std::string(path,PathUtil::getTrailSlashPtr(path)).data());
    path = "///a///";
    EXPECT_STREQ("///a",std::string(path,PathUtil::getTrailSlashPtr(path)).data());

    path = "a";
    EXPECT_STREQ("a",std::string(path,PathUtil::getTrailSlashPtr(path)).data());
    path = "a/";
    EXPECT_STREQ("a",std::string(path,PathUtil::getTrailSlashPtr(path)).data());
    path = "a///";
    EXPECT_STREQ("a",std::string(path,PathUtil::getTrailSlashPtr(path)).data());
}

}

#endif







////////////////////////////PathUtil Implement End//////////////////////////////


